package com.unionpay.acp.sdk;

import cn.demoncat.util.exception.BizRuntimeException;
import cn.demoncat.util.io.CloseUtil;
import cn.demoncat.util.lang.JsonUtil;
import cn.demoncat.util.lang.constant.CharsetConstant;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.zip.Inflater;

/**
 * SDK工具类
 * 
 * @author 银联
 *
 * @since 2016-7-22
 */
public class SDKUtil {

	private static final Logger LOG = LoggerFactory.getLogger(SDKUtil.class);
	
	/**
	 * 计算签名
	 * 
	 * @param data 待签名数据Map
	 * 
	 * @return 签名是否成功
	 */
	public static Map<String, String> sign(Map<String, String> data) {
		// 设置签名证书序列号
		data.put(SDKConstants.param_certId, CertUtil.getSignCertId());
		// 将Map信息转换成key1=value1&key2=value2的形式
		String stringData = coverMap2String(data);
		byte[] byteSign;
		String stringSign;
		try {
			// 通过SHA256进行摘要并转16进制
			byte[] signDigest = SecureUtil.sha256X16(stringData, CharsetConstant.UTF8);
			byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft256(CertUtil.getSignCertPrivateKey(), signDigest));
			stringSign = new String(byteSign);
			// 设置签名域值
			data.put(SDKConstants.param_signature, stringSign);
			return data;
		} catch (Exception e) {
			LOG.error("请求签名失败：\r\n" + JsonUtil.toStr(data), e);
			throw new BizRuntimeException("请求签名失败", e);
		}
	}

	/**
	 * 验证签名
	 * 
	 * @param data 返回报文数据
	 * 
	 * @return
	 */
	public static void validate(Map<String, String> data) {
		String encoding = data.get(SDKConstants.param_encoding);
		// 获取公钥信息转换成公钥对象
		String strCert = data.get(SDKConstants.param_signPubKeyCert);
		X509Certificate x509Cert = CertUtil.genCertificateByStr(strCert);
		if(x509Cert == null) {
			throw new BizRuntimeException("响应验签失败：获取公钥信息失败");
		}
		// 验证证书链
		if (!CertUtil.verifyCertificate(x509Cert)) {
			throw new BizRuntimeException("响应验签失败：验证公钥证书失败");
		}
		// 验签
		String stringSign = data.get(SDKConstants.param_signature);
		// 将Map信息转换成key1=value1&key2=value2的形式
		String stringData = coverMap2String(data);
		try {
			// 验证签名需要用银联发给商户的公钥证书
			if (!SecureUtil.validateSignBySoft256(x509Cert.getPublicKey(), SecureUtil.base64Decode(stringSign.getBytes(encoding)), SecureUtil.sha256X16(stringData, encoding))) {
				throw new BizRuntimeException("验证数据签名失败");
			}
		} catch (Exception e) {
			LOG.error("响应验签失败：\r\n" + JsonUtil.toStr(data), e);
			throw new BizRuntimeException("响应验签失败：" + e.getMessage(), e);
		}
	}

	/**
	 * 将Map中的数据转换成key1=value1&key2=value2的形式 不包含签名域signature
	 * 
	 * @param data
	 *            待拼接的Map数据
	 * @return 拼接好后的字符串
	 */
	public static String coverMap2String(Map<String, String> data) {
		TreeMap<String, String> tree = new TreeMap<>();
		Iterator<Entry<String, String>> it = data.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, String> en = it.next();
			if (SDKConstants.param_signature.equals(en.getKey().trim())) {
				continue;
			}
			tree.put(en.getKey(), en.getValue());
		}
		it = tree.entrySet().iterator();
		StringBuilder sf = new StringBuilder();
		Entry<String, String> en;
		while (it.hasNext()) {
			en = it.next();
			sf.append(en.getKey()).append(SDKConstants.EQUAL).append(en.getValue()).append(SDKConstants.AMPERSAND);
		}
		return sf.substring(0, sf.length() - 1);
	}

	/**
	 * 将形如key=value&key=value的字符串转换为相应的Map对象
	 * 
	 * @param result
	 * @return
	 */
	public static Map<String, String> convertResultStringToMap(String result) {
		result = StringUtils.trimToNull(result);
		if (StringUtils.isNotBlank(result)) {
			if (result.startsWith("{") && result.endsWith("}")) {
				result = result.substring(1, result.length() - 1);
			}
			return parseQString(result);
		}
		return null;
	}

	
	/**
	 * 解析应答字符串，生成应答要素
	 * 
	 * @param str
	 *            需要解析的字符串
	 * @return 解析的结果map
	 * @throws UnsupportedEncodingException
	 */
	public static Map<String, String> parseQString(String str) {
		Map<String, String> map = new HashMap<>();
		int len = str.length();
		StringBuilder temp = new StringBuilder();
		char curChar;
		String key = null;
		boolean isKey = true;
		boolean isOpen = false;//值里有嵌套
		char openName = 0;
		if(len>0){
			for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
				curChar = str.charAt(i);// 取当前字符
				if (isKey) {// 如果当前生成的是key
					
					if (curChar == '=') {// 如果读取到=分隔符 
						key = temp.toString();
						temp.setLength(0);
						isKey = false;
					} else {
						temp.append(curChar);
					}
				} else  {// 如果当前生成的是value
					if(isOpen){
						if(curChar == openName){
							isOpen = false;
						}
						
					}else{//如果没开启嵌套
						if(curChar == '{'){//如果碰到，就开启嵌套
							isOpen = true;
							openName ='}';
						}
						if(curChar == '['){
							isOpen = true;
							openName =']';
						}
					}
					
					if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域，这时将map里添加
						putKeyValueToMap(temp, isKey, key, map);
						temp.setLength(0);
						isKey = true;
					} else {
						temp.append(curChar);
					}
				}
				
			}
			putKeyValueToMap(temp, isKey, key, map);
		}
		return map;
	}

	private static void putKeyValueToMap(StringBuilder temp, boolean isKey,
			String key, Map<String, String> map) {
		if (isKey) {
			key = temp.toString();
			if (key.length() == 0) {
				throw new RuntimeException("QString format illegal");
			}
			map.put(key, "");
		} else {
			if (key.length() == 0) {
				throw new RuntimeException("QString format illegal");
			}
			map.put(key, temp.toString());
		}
	}

	/**
	 * 解压缩
	 * 
	 * @param inputByte
	 *            byte[]数组类型的数据
	 * @return 解压缩后的数据
	 * @throws IOException
	 */
	public static byte[] inflater(final byte[] inputByte) {
		int compressedDataLength;
		Inflater compresser = new Inflater(false);
		compresser.setInput(inputByte, 0, inputByte.length);
		ByteArrayOutputStream o = new ByteArrayOutputStream(inputByte.length);
		byte[] result = new byte[1024];
		try {
			while (!compresser.finished()) {
				compressedDataLength = compresser.inflate(result);
				if (compressedDataLength == 0) {
					break;
				}
				o.write(result, 0, compressedDataLength);
			}
		} catch (Exception e) {
			throw new BizRuntimeException("解压文件失败", e);
		} finally {
			CloseUtil.close(o);
		}
		compresser.end();
		return o.toByteArray();
	}

	
}
